home *** CD-ROM | disk | FTP | other *** search
/ PsL Monthly 1993 December / PSL Monthly Shareware CD-ROM (December 1993).iso / prgmming / dos / pascal / coun.com / COUN.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1990-06-12  |  13.4 KB  |  357 lines

  1. UNIT COUN;
  2.  
  3. {  Sorry about the name but, what else do you call a COmpress/UNCompress
  4.    routine.
  5.  
  6.    Anyway, this routine was created to compress a PASCAL RECORD which has
  7.    STRING variables.  To do this I mearly pack the rest of the data in behind
  8.    the used portion of a TPas String.  Example:
  9.  
  10.           CUSTREC = RECORD
  11.             LNAME : STRING[40];
  12.             FNAME : STRING[40];
  13.  
  14.          CUSTREC.FNAME := 'CARL';
  15.          CUSTREC.LNAME := 'FRANZ';
  16.  
  17.    The record CUSTREC takes up 82 bytes.  When this routine is done with it
  18.    it takes up 11 bytes. '$CARL$FRANZ' where $ is the length Byte of the
  19.    string (04H and 05H respectively).
  20.  
  21.    The routines COMRESS and UNCOMPRESS accepts a map of the RECORD, the RECORD,
  22.    and a BYTE ARRAY large enough to accept the RECORD.
  23.  
  24.    COMPRESS will return the length of the compressed record as an integer.
  25.  
  26.    NOTE: DO NOT attempt to compress/decompress in place ie. use the same
  27.          variable for both INDATA and OUTDATA.  COMPRESS will probably work
  28.          but DECOMPRESS absolutely will NOT work.                             }
  29.  
  30. INTERFACE
  31.  
  32. VAR
  33.    COUNERR : BYTE;
  34.    COUNWHR : BYTE;
  35.  
  36. FUNCTION Compress(Cmap : STRING; VAR InData; VAR OutData) : INTEGER;
  37. {     where:  Cmap - a string variable containing the map of the record
  38.               InData  - your record
  39.               OutData - a buffer where the compresses record is returned
  40.  
  41.      Returns - an integer whis is the length of the compressed record        }
  42.  
  43. PROCEDURE UnCompress(Cmap : STRING; VAR InData; VAR OutData);
  44. {     where:  Cmap - a string variable containing the map of the record
  45.               InData  - your compressed record
  46.               OutData - whereever you what the uncompressed record returned  }
  47.  
  48.  
  49. IMPLEMENTATION
  50.  
  51. TYPE
  52.      BA = ARRAY [1..32000] OF BYTE; {array type for bopping thru the data}
  53.  
  54.      L1 = RECORD        { rec for parsing tokens from record map }
  55.         Ctype : CHAR;   {the type of data to be moved}
  56.         Size : INTEGER; {the size of the data to be moved, or array size,
  57.                          or if ']' the position of the matching '[' }
  58.         Decr : INTEGER  {This is used as a down counter for iterating an array }
  59.      END;
  60.  
  61.      L2 = RECORD        { RECORD necesssary when parsing multi-level arrays }
  62.         Rtn : BYTE;     {the L1 index to return to on finding the matching '['}
  63.      END;
  64.  
  65.    lp1 = ARRAY [1..100] OF l1;  {parsing ARRAY OF TYPE L1 }
  66.    lp2 = ARRAY [1..100] OF l2;  {nested array stack}
  67.  
  68. CONST numset : SET OF CHAR = ['0'..'9'];   {is this token a number ?}
  69.  
  70. VAR
  71.    lpa1     : ^lp1;    {declaration of a pointer to the parsing ARRAY}
  72.    lpa2     : ^lp2;    {declaration of a pointer to the ARRAY parsing stack }
  73.    done,               {next after last element in LPA1 when parsing is done }
  74.    Lpa1Indx,           {index for LPA1}
  75.    Lpa2Indx : BYTE;    {index for LPA2}
  76.    OutPtr   : ^BA;     {pointer to the output byte array}
  77.    InPtr    : ^BA;     {pointer to your RECORD, redefines as a byte array}
  78.    OutIndx,            {index to the output ARRAY }
  79.    InIndx   : INTEGER; {index to your record when viewed as a BYTE ARRAY}
  80.  
  81.  
  82. {  This PROCEDURE parses the RECORD Map into something useful, namely an
  83.    ARRAY OF directions to how to Compress or UnCompress your RECORD.  If
  84.    this sounds simple just try to process this in your head '[25[25[25s]]]'.
  85.    If you can do it then you should be writing compilers.                   }
  86.  
  87. PROCEDURE ParseCMap(CMap: STRING);
  88. VAR
  89.    BracketCnt,
  90.    CIndx : BYTE;
  91.    Lp1t  : l1;
  92.    Lp2t  : l2;
  93.    token : CHAR;
  94.  
  95. FUNCTION GetNum : INTEGER;
  96. {  GetNum parses the CMap for numbers, it turns a stringed number into an
  97.    Integer number.  I could have used VAL but VAL is such a cluge}
  98. VAR
  99.     SSize : INTEGER; {String size - intermediate hold when evaluating the nums}
  100.     NToken : char;   {The character currently being evaluated from CMap }
  101. BEGIN
  102.     SSize := 0;
  103.     NToken := CMap[SUCC(CIndx)];
  104.     IF NOT (NToken IN NumSet) THEN
  105.        SSize := 255
  106.     ELSE
  107.        WHILE (NToken IN NumSet) DO
  108.           BEGIN
  109.           SSize := SSize * 10 + (ORD(NToken) - ORD('0'));
  110.           INC(CIndx);
  111.           NToken := CMap[SUCC(CIndx)];
  112.           END;
  113.     GetNum := SSize;
  114. END;
  115.  
  116.  
  117. FUNCTION GetToken : boolean;
  118. {  GetToken gets a token from the CMap and makes some decisions about it, like
  119.    is this a STRING, should I try to find a number associated with it, etc.
  120.    It then loads a L1 type record with pertinent information about the token. }
  121. BEGIN
  122.     IF cindx > ord(CMap[0]) THEN  {we are at the end of the record map}
  123.        BEGIN
  124.             IF NOT (bracketCnt = 0) THEN   {mismatched brackets?}
  125.                COUNERR := 3;
  126.             GetToken := FALSE;
  127.             exit;
  128.        END;
  129.     token := UpCase(CMap[cindx]);    {get a character from the record map}
  130.     lp1t.Decr := 0;
  131.     lp1t.Ctype := token;
  132.     CASE token OF
  133.     'S'      : BEGIN  {Is this a STRING?}
  134.                  lp1t.size := GetNum;
  135.                END;
  136.     '['      : BEGIN  {Is this the start of an array?}
  137.                  lp1t.size := GetNum;
  138.                  INC(BracketCnt);
  139.                END;
  140.     ']'      : BEGIN  {Is this the END OF an ARRAY definition }
  141.                  lp1t.size := 0;
  142.                  DEC(BracketCnt);
  143.                END;
  144.     'W', 'I' : BEGIN  {Is this a Word or INTEGER}
  145.                  lp1t.size := sizeof(word);
  146.                END;
  147.     'L', 'P' : BEGIN  {is this a LONGINT or POINTER}
  148.                  lp1t.size := sizeof(pointer);
  149.                END;
  150.     'C', 'B' : BEGIN  {Is this a CHAR or BYTE}
  151.                  lp1t.size := sizeof(BYTE);
  152.                END;
  153.     'R'      : BEGIN  {is this a real number?}
  154.                  lp1t.size := sizeof(Real);
  155.                END;
  156.     ','      : BEGIN  {is this a comma?}
  157.                  lp1t.size := 0;
  158.                END;
  159.     ELSE BEGIN
  160.              IF (token in numset) THEN {Is it a data length?}
  161.                  BEGIN
  162.                    lp1t.Ctype := ' ';
  163.                    DEC(CIndx);
  164.                    Lp1T.size := GetNum;
  165.                  END
  166.              ELSE
  167.                 BEGIN
  168.                    COUNERR := 2;
  169.                    COUNWHR := CIndx;
  170.                 END;
  171.          END;
  172.     END;
  173.     INC(CIndx);
  174.     GetToken := True;
  175. END;
  176.  
  177. {  The ParseCMap mainline loads the parsing arrays }
  178.  
  179. BEGIN
  180.    Lpa1Indx := 1; Lpa2Indx := 1; CIndx := 1; BracketCnt:= 0;
  181.    WHILE (GetToken) DO
  182.    BEGIN
  183.        IF (lp1t.Ctype = '[') THEN {if start of array load l2 with return #}
  184.             BEGIN
  185.               lp2t.rtn := Lpa1Indx;
  186.               lpa2^[Lpa2Indx] := lp2t;
  187.               INC(Lpa2Indx);
  188.             END;
  189.        IF (lp1t.Ctype = ']') THEN {if end of array get return # from l2}
  190.             BEGIN
  191.               DEC(lpa2indx);
  192.               lp1t.size := lpa2^[lpa2indx].rtn;
  193.             END;
  194.        if lp1t.ctype <> ',' then  {if not a comma load parse array with data}
  195.        begin
  196.            lpa1^[Lpa1Indx] := lp1t;
  197.            INC(Lpa1Indx);
  198.        end;
  199.    END;
  200.    done := lpa1Indx;
  201. END;
  202.  
  203.  
  204.  
  205. {  The rest of this mess is the actual Compress/UnCompress logic.  It runs
  206.    thru the Token Array and compresses strings, moves other types, and
  207.    processes the arrays within your record.                                   }
  208.  
  209. PROCEDURE CompressAny(size : BYTE);
  210. {  This routine moves any non STRING data to the output BYTE ARRAY  }
  211. BEGIN
  212.   MOVE(InPtr^[InIndx], OutPtr^[OutIndx],Size);
  213.   InIndx := InIndx + Size;
  214.   OutIndx := OutIndx + Size;
  215. END;
  216.  
  217. PROCEDURE CompressStr(size : BYTE);
  218. {  This routine moves any STRING data to the output BYTE ARRAY  }
  219. VAR
  220.    SLen : BYTE;
  221. BEGIN
  222.     SLen := InPtr^[InIndx];     {string length is first byte of string}
  223.     INC(SLen);                  {incremint past the length}
  224.     MOVE(InPtr^[InIndx], OutPtr^[OutIndx], SLen);   {move the string}
  225.     InIndx := InIndx + Size + 1; {set InIndx past the allocated length}
  226.     OutIndx := OutIndx + SLen; {set the outindex past the actual data length}
  227. END;
  228.  
  229. PROCEDURE UnCompressAny(size : BYTE);
  230. {  This routine moves any non STRING data to the output byte array - UnCompress}
  231. BEGIN
  232.    MOVE(InPtr^[InIndx], OutPtr^[OutIndx],Size);  {move the data}
  233.    InIndx := InIndx + Size;     {increment both the InIndx and OutIndx  }
  234.    OutIndx := OutIndx + Size;
  235. END;
  236.  
  237. PROCEDURE UnCompressStr(size : INTEGER);
  238. {  This routine moves any STRING data to the output byte array - UnCompress}
  239. VAR
  240.    SLen : INTEGER;
  241.    SStart : INTEGER;
  242. BEGIN
  243.    SLen := InPtr^[InIndx] + 1;     {get the string length from the byte array}
  244.    MOVE(InPtr^[InIndx], OutPtr^[OutIndx], SLen);   {move the string}
  245.    OutIndx := OutIndx + Size + 1;   {inc the InIndx and OutIndx past this data}
  246.    InIndx := InIndx + Slen;
  247. END;
  248.  
  249. {  This is the parse table processor for both the compress and uncompress.
  250.    It steps thru the Parsed token array a step at a time and makes decisions
  251.    about what to do.  There are 4 decisions to make: 1) If this is a STRING
  252.    then process it; 2) IF this is some other variable type then move it;
  253.    3) IF this is a Start-Array type, then set up the counter; 4) If this is a
  254.    End-Array type then either decrament the counter, or if counter is 0 then
  255.    go on to the rest of the data.                                             }
  256.  
  257. PROCEDURE ProcessTbl(cd : CHAR);
  258.  
  259. BEGIN
  260.    lpa1indx := 1;
  261.    REPEAT
  262.        CASE lpa1^[Lpa1Indx].CType OF
  263.        '[' : BEGIN   {if array start move array count to decr}
  264.                 lpa1^[lpa1indx].Decr := pred(lpa1^[Lpa1Indx].size);
  265.              END;
  266.        ']' : BEGIN   {if end of array either dec the Decr or if 0 go on }
  267.                 IF (lpa1^[lpa1^[lpa1indx].size].Decr > 0) THEN
  268.                 BEGIN
  269.                     lpa1indx := lpa1^[lpa1indx].size; {reset index to matching
  270.                                                        start-array element}
  271.                     DEC(lpa1^[lpa1indx].Decr);        {subtract 1 from decr}
  272.                 END;
  273.              END;
  274.        'S' : BEGIN   {either compress or uncompress a string}
  275.                 IF (cd = 'C') THEN
  276.                    CompressStr(lpa1^[Lpa1Indx].size)
  277.                 ELSE
  278.                    UnCompressStr(lpa1^[Lpa1Indx].size);
  279.              END;
  280.        ELSE  IF (cd = 'C') THEN {either compress or uncompress other type}
  281.                 CompressAny(lpa1^[Lpa1Indx].size)
  282.              ELSE
  283.                 UnCompressAny(lpa1^[Lpa1Indx].size);
  284.        END;
  285.        INC(Lpa1Indx);
  286.    UNTIL (lpa1indx = done);
  287. END;
  288.  
  289. {$F+} FUNCTION HEAPFUNC(Size:Word) : INTEGER; {$F-}
  290. { Boy is Turbo Pascal stupid sometimes, ya know.
  291.   This is just to keep the New from blowing up on not enough memory
  292.      should the occasion ever arise                                 }
  293. BEGIN
  294.    HeapFunc := 1; {return null pointer instead of abending on error}
  295. END;
  296.  
  297. PROCEDURE UnCompress(CMap : STRING;VAR InData;VAR OutData);
  298. { UnCompress gets addressability to your compressed data and output RECORD,
  299.   gets memory for the parse arrays, calls ParseCMap, uncompresses the data }
  300. VAR
  301.    BAI     : BA absolute InData;    {your compressed record}
  302.    BAO     : BA absolute OutData;   {your uncompressed record}
  303.    Hptr    : Pointer;
  304.  
  305. BEGIN
  306.      Hptr := HeapError;                {see page 200 of the 5.0 Tpas manual }
  307.      HeapError := @HeapFunc;
  308.      COUNERR := 0; COUNWHR := 0;        {zero the error codes}
  309.      InPtr := @BAI; OutPtr := @BAO;     {Turn you data into byte arrays}
  310.      InIndx := 1; OutIndx := 1;         {set indexes to 1}
  311.      New(lpa1); New(lpa2);              {allocate space for parsing arrays }
  312.      IF (lpa1 = nil) or (lpa2 = nil) THEN {check for mem allocation errors }
  313.          BEGIN
  314.             COUNERR := 1;
  315.             IF (lpa1 <> nil) THEN Dispose(lpa1);
  316.          END
  317.      ELSE BEGIN
  318.         parseCMap(CMap);                 {parse the Cmap input into lpa1}
  319.         if COUNERR  = 0 then             {if no errors in parseing}
  320.             ProcessTbl('D');             {decompress the record}
  321.         Dispose(lpa1); Dispose(lpa2);    {dispose of the parse arrays }
  322.      END;
  323.      HeapError := HPtr; {reset the heap error whatever (p200 tpas5.0 manual)}
  324. END;
  325.  
  326. FUNCTION Compress(CMap : STRING;VAR InData;VAR OutData) : INTEGER;
  327. { Compress gets addressability to your RECORD and output ARRAY as BYTE arrays,
  328.   gets memory FOR the parse arrays, calls ParseCMap, decompresses the data }
  329. VAR
  330.    BAI     : BA absolute InData;     {your record as a byte array}
  331.    BAO     : BA absolute OutData;    {your record compresses as a byte array}
  332.    Hptr    : Pointer;                {never mind}
  333.  
  334. BEGIN
  335.      Hptr := HeapError;              {see page 200 of the 5.0 Tpas manual}
  336.      HeapError := @HeapFunc;
  337.      COUNERR := 0; COUNWHR := 0;     {zero the error codes}
  338.      InPtr := @BAI; OutPtr := @BAO;  {turn you data into byte arrays}
  339.      InIndx := 1; OutIndx := 1;      {reset the indexes}
  340.      New(lpa1); New(lpa2);           {allocate space for the parse arrays}
  341.      IF (lpa1 = nil) or (lpa2 = nil) THEN {if allocation error}
  342.          BEGIN
  343.             COUNERR := 1;
  344.             IF (lpa1 <> nil) THEN Dispose(lpa1);
  345.          END
  346.      ELSE BEGIN
  347.         parseCMap(CMap);              {parse the record map}
  348.         if COUNERR  = 0 then          {if no parse errors}
  349.             ProcessTbl('C');          {compress your record}
  350.         Dispose(lpa1); Dispose(lpa2); {dispose of the parsing arrays}
  351.         Compress := pred(OutIndx);    {return the size of compressed record}
  352.      END;
  353.      HeapError := HPtr;               {reset the heap error process pointer}
  354. END;
  355.  
  356. END.
  357.